/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "authority_manager.h"

#include <fstream>

#include "securec.h"
#include "ipc_skeleton.h"
#include "file_ex.h"

#include "device_profile_log.h"

namespace OHOS {
namespace DeviceProfile {
namespace {
const std::string TAG = "AuthorityManager";

constexpr int32_t BUF_SIZE = 256;
constexpr uint32_t MAX_AUTH_VALUE = 3;
const std::string INTERFACES = "interfaces";
const std::string SERVICES = "services";
const std::string SERVICES_ALL = "all";
const std::string SERVICES_SPECIFIC = "specific";
const std::string SERVICES_PREFIX = "prefix";
const std::string AUTHORITY_JSON_PATH = "/system/etc/deviceprofile/authority.json";
}

IMPLEMENT_SINGLE_INSTANCE(AuthorityManager);

bool AuthorityManager::Init()
{
    if (!LoadAuthorityCfg(AUTHORITY_JSON_PATH)) {
        return false;
    }
    HILOGI("init succeeded");
    PrecheckAuthority();
    std::string procName = GetCallingProcName();
    CheckAuthority(procName);
    CheckAuthority(procName, AuthValue::AUTH_R);
    CheckAuthority(procName, AuthValue::AUTH_R, {"system"});
    CheckAuthority(procName, AuthValue::AUTH_RW, {"cameraFront123"});
    return true;
}

bool AuthorityManager::LoadAuthorityCfg(const std::string& filePath)
{
    std::ifstream ifs(filePath.c_str());
    if (!ifs.good()) {
        HILOGE("load json file failed");
        return false;
    }
    authJson_ = nlohmann::json::parse(ifs, nullptr, false);
    if (authJson_.is_discarded()) {
        HILOGE("parse failed");
        return false;
    }
    HILOGI("auth json %{public}s", authJson_.dump().c_str());
    return true;
}

bool AuthorityManager::CheckAuthority(const std::string& procName, AuthValue authVal,
    const std::vector<std::string>& serviceIds)
{
    bool hasEmpty = std::any_of(serviceIds.begin(), serviceIds.end(),
        [](const auto& serviceId) { return serviceId.empty(); });
    if (hasEmpty) {
        HILOGE("has empty serviceId");
        return false;
    }

    if (!authJson_.contains(procName)) {
        HILOGE("can't find entry for proc:%{public}s", procName.c_str());
        return false;
    }
    auto& servicesJson = authJson_[procName][SERVICES];
    if (servicesJson == nullptr) {
        HILOGE("can't find services entry");
        return false;
    }

    // in case where not involved with data
    if (authVal == AuthValue::AUTH_NONE) {
        HILOGI("check pass, not involved with data");
        return true;
    }
    if (servicesJson.contains(SERVICES_ALL)) {
        // set AUTH_R checked
        authVal = static_cast<AuthValue>(static_cast<uint16_t>(authVal) &
            ~static_cast<uint16_t>(AuthValue::AUTH_R));
    }
    if (authVal == AuthValue::AUTH_NONE) {
        HILOGI("check pass, SERVICES_ALL with read");
        return true;
    }
    // in case where no serviceIds provided, which means SERVICES_ALL; and
    // currently SERVICES_ALL is only allowed with AUTH_R
    if (serviceIds.empty()) {
        HILOGE("check failed, SERVICES_ALL should only be with read");
        return false;
    }

    if (!CheckServices(servicesJson, authVal, serviceIds)) {
        return false;
    }
    return true;
}

bool AuthorityManager::CheckServices(const nlohmann::json& servicesJson,
   AuthValue authVal, const std::vector<std::string>& serviceIds)
{
    const auto& specificSvcsJson = servicesJson[SERVICES_SPECIFIC];
    const auto& prefixSvcsJson = servicesJson[SERVICES_PREFIX];
    if (specificSvcsJson == nullptr && prefixSvcsJson == nullptr) {
        HILOGE("check failed, no specific or prefix service");
        return false;
    }
    for (const auto& serviceId : serviceIds) {
        if ((specificSvcsJson != nullptr) &&
            CheckSpecificServiceAuth(specificSvcsJson, serviceId, authVal)) {
            continue;
        }
        if ((prefixSvcsJson != nullptr) &&
            CheckPrefixServiceAuth(prefixSvcsJson, serviceId, authVal)) {
            continue;
        }
        HILOGE("denied to access service:%{public}s", serviceId.c_str());
        return false;
    }
    HILOGI("check pass, authorities statisfied");
    return true;
}

bool AuthorityManager::CheckSpecificServiceAuth(const nlohmann::json& specificSvcsJson,
    const std::string& serviceId, AuthValue authVal)
{
    if (!specificSvcsJson.contains(serviceId)) {
        return false;
    }
    auto& serviceJson = specificSvcsJson[serviceId];
    HILOGI("auth %{public}d", serviceJson.get<uint32_t>());
    return true;
}

bool AuthorityManager::CheckPrefixServiceAuth(const nlohmann::json& prefixSvcsJson,
    const std::string& serviceId, AuthValue authVal)
{
    for (auto& [key, value] : prefixSvcsJson.items()) {
        HILOGI("auth %{public}d", value.get<uint32_t>());
        if ((serviceId.compare(0, key.size(), key) == 0)) {
            HILOGI("service:%{public}s, prefix:%{public}s", serviceId.c_str(), key.c_str());
            return true;
        }
    }
    return false;
}

void AuthorityManager::PrecheckAuthority()
{
    HILOGI("v6");
    for (auto& [proc, procJson] : authJson_.items()) {
        if (!procJson.contains(SERVICES)) {
            continue;
        }
        auto& servicesJson = procJson[SERVICES];
        if (servicesJson.contains(SERVICES_ALL)) {
            // currently only read permission is allowed being with SERVICES_ALL
            if (!PrecheckService(servicesJson[SERVICES_ALL], true)) {
                HILOGW("remove service all");
                servicesJson.erase(SERVICES_ALL);
            }
        }
        if (servicesJson.contains(SERVICES_SPECIFIC)) {
            PrecheckServices(servicesJson[SERVICES_SPECIFIC]);
        }
        if (servicesJson.contains(SERVICES_PREFIX)) {
            PrecheckServices(servicesJson[SERVICES_PREFIX]);
        }
    }
    HILOGI("auth json %{public}s", authJson_.dump().c_str());
}

void AuthorityManager::PrecheckServices(nlohmann::json& servicesJson)
{
    for (auto iter = servicesJson.begin(); iter != servicesJson.end();) {
        if (!PrecheckService(iter.value(), false)) {
            HILOGW("service:%{public}s is invalid, deleted", iter.key().c_str());
            servicesJson.erase(iter++);
        } else {
            iter++;
        }
    }
}

bool AuthorityManager::PrecheckService(const nlohmann::json& authValJson, bool readOnly)
{
    if (!authValJson.is_number_unsigned()) {
        HILOGE("bad type");
        return false;
    }
    auto authVal = authValJson.get<uint32_t>();
    if (authVal > MAX_AUTH_VALUE || authVal == AuthValue::AUTH_NONE) {
        HILOGE("invalid range");
        return false;
    }
    if (readOnly && authVal != AuthValue::AUTH_R) {
       HILOGE("read only"); 
       return false;
    }
    return true;
}

std::string AuthorityManager::GetCallingProcName()
{
    char buf[BUF_SIZE] = {0};
    int32_t pid = IPCSkeleton::GetCallingPid();
    HILOGI("pid = %{public}d", pid);
    (void)snprintf_s(buf, BUF_SIZE, BUF_SIZE - 1, "/proc/%d/cmdline", pid);

    std::string procName;
    if (!LoadStringFromFile(std::string(buf, strlen(buf)), procName)) {
        HILOGE("load string failed");
        return procName;
    }
    auto pos = procName.find_last_not_of(" \n\r\t");
    if (pos != std::string::npos) {
        procName.erase(pos + 1);
    }
    HILOGI("calling proc is %{public}s", procName.c_str());
    return procName.c_str();
}
} // namespace DeviceProfile
} // namespace OHOS